package com.fangcang.log.tracelog;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;
import org.apache.log4j.MDC;

import com.fasterxml.jackson.databind.ObjectMapper;

public class TraceKit {
	protected static Logger logger = Logger.getLogger(TraceKit.class);
	private static final ThreadLocal<Map<String, Integer>> TRACE_ERROR_NUM = new ThreadLocal<Map<String, Integer>>();
	private static final ThreadLocal<Map<String, Integer>> TRACE_SUCCESS_NUM = new ThreadLocal<Map<String, Integer>>();
	
	private static final ObjectMapper objectMapper = new ObjectMapper();
	
	// ============generator traceId================================
	/**
	 * @Title: genTraceId
	 * @Description: 生成traceId
	 * @return String 返回类型
	 * @throws
	 */
	public static final String genTraceId() {
		int workerId = (int) (Math.random() * 30);
		int datacenterId = (int) (Math.random() * 30);
		SnowflakeIdWorker idWorker = new SnowflakeIdWorker(workerId, datacenterId);
		return idWorker.nextId() + "";
	}
	
	
	/**
	 * 从MDC中获取traceId
	 * @return
	 */
	public static final String getTraceId() {
		String traceId = (String) MDC.get(TraceLog.TRACE_ID);
		return traceId;
	}
	
	
	/**
	 * @Title: start
	 * @Description: 打印监控起点日志
	 * @param log 日志对象
	 * @param logData  日志数据对象
	 * @return void 返回类型
	 * @throws
	 */
	public static final void start(Logger log, TraceLog logData) {
		try {
			String traceId = (String) MDC.get(TraceLog.TRACE_ID);
			if(traceId == null || "".equals(traceId)){
				traceId = genTraceId();
				MDC.put(TraceLog.TRACE_ID, traceId);
			}
			logData.setClassMethod(log.getName());
			logData.setTraceId(traceId);
			logData.setStartTime(System.currentTimeMillis());
			logData.setLogType(TraceLog.TYPE_START);
			logData.setE(null);
			
			log.log(Xtrace.XTRACE, objectMapper.writeValueAsString(logData));
		} catch (Exception e) {
			logger.error("打印监控start日志出现异常", e);
		}
	}
	/**
	 * @Title: subStart
	 * @Description: 打印监控分支起点日志
	 * @param log 日志对象
	 * @param logData 日志数据对象
	 * @param parentId 父分支的traceId
	 * @return void 返回类型
	 * @throws
	 */
	public static final void subStart(Logger log, TraceLog logData, String parentId) {
		try {
			String traceId = genTraceId();
			MDC.put(TraceLog.TRACE_ID, traceId);
			logData.setClassMethod(log.getName());
			logData.setParentId(parentId);
			logData.setTraceId(traceId);
			logData.setStartTime(System.currentTimeMillis());
			logData.setLogType(TraceLog.TYPE_START);
			logData.setE(null);
			
			log.log(Xtrace.XTRACE, objectMapper.writeValueAsString(logData));
		} catch (Exception e) {
			logger.error("打印监控subStart日志出现异常", e);
		}
	}
	
	
	/**
	 * @Title: subStart
	 * @Description: 打印监控分支起点日志
	 * @param log 日志对象
	 * @param logData 日志数据对象
	 * @return void 返回类型
	 * @throws
	 */
	public static final TraceLog subStart(Logger log, TraceLog logData) {
		String parentId = (String)MDC.get(TraceLog.TRACE_ID);
		try {
			String traceId = genTraceId();
			MDC.put(TraceLog.TRACE_ID, traceId);
			logData.setClassMethod(log.getName());
			logData.setParentId(parentId);
			logData.setTraceId(traceId);
			logData.setStartTime(System.currentTimeMillis());
			logData.setLogType(TraceLog.TYPE_START);
			logData.setE(null);
			
			log.log(Xtrace.XTRACE, objectMapper.writeValueAsString(logData));
			
		} catch (Exception e) {
			logger.error("打印监控subStart日志出现异常", e);
		}
		return logData;
	}
	
	/**
	 * @Title: end
	 * @Description: 打印监控正常结束日志【该方法一定要调用，否则会存在内存泄漏的问题】（确保同一线程同一traceId，正常日志和异常日志只会打印其中一种）
	 * @param log
	 * @param logData
	 * @return void 返回类型
	 * @throws
	 */
	public static final void end(Logger log, TraceLog logData) {
		try {
			String traceId = (String) MDC.get(TraceLog.TRACE_ID);
			Map<String, Integer> map = TRACE_SUCCESS_NUM.get();
			Map<String, Integer> map3 = TRACE_ERROR_NUM.get();
			if ((map == null || map.get(traceId) == null || map.get(traceId) == 0)
					&& (map3 == null || map3.get(traceId) == null ||map3.get(traceId) == 0)) {
				logData.setTraceId(traceId);
				logData.setClassMethod(log.getName());
				logData.setEndTime(System.currentTimeMillis());
				logData.setTotalTime(System.currentTimeMillis()-logData.getStartTime());
				logData.setLogType(TraceLog.TYPE_END);
				logData.setE(null);
				
				log.log(Xtrace.XTRACE, objectMapper.writeValueAsString(logData));
				Map<String, Integer> map2 = new HashMap<String, Integer>();
				map2.put(traceId, 1);
				TRACE_SUCCESS_NUM.set(map2);
			}
		} catch (Exception e) {
			logger.error("打印监控end日志出现异常", e);
		} finally {
			String pid = logData.getParentId();
			if(pid!=null && !"".equals(pid)){
				MDC.put(TraceLog.TRACE_ID, pid);
			}else{
				MDC.clear();
				TRACE_SUCCESS_NUM.remove();
				TRACE_ERROR_NUM.remove();
			}
		}
	}
	
	/**
	 * @Title: error
	 * @Description: 打印监控异常日志（确保同一线程同一traceId，只会打印一条异常日志）
	 * @param log
	 * @param logData
	 * @return void 返回类型
	 * @throws
	 */
	public static final void error(Logger log, TraceLog logData) {
		try {
			String traceId = (String) MDC.get(TraceLog.TRACE_ID);
			Map<String, Integer> map = TRACE_ERROR_NUM.get();
			if (map == null || map.get(traceId)==null || map.get(traceId) == 0) {
				logData.setTraceId(traceId);
				logData.setClassMethod(log.getName());
				logData.setEndTime(System.currentTimeMillis());
				logData.setTotalTime(System.currentTimeMillis()-logData.getStartTime());
				logData.setLogType(TraceLog.TYPE_ERROR);
				logData.setRemark(logData.getRemark()==null?""+getStackTrace(logData.getE()):logData.getRemark()+" "+getStackTrace(logData.getE()));
				logData.setE(null);
				
				log.log(Xtrace.XTRACE, objectMapper.writeValueAsString(logData));
				Map<String, Integer> map2 = new HashMap<String, Integer>();
				map2.put(traceId, 1);
				TRACE_ERROR_NUM.set(map2);
			}
		} catch (Exception e) {
			logger.error("打印监控error日志出现异常", e);
		}
	}
	
	
	/**
	 * @Title: error
	 * @Description: 打印监控异常日志（确保同一线程同一traceId，可打印多条异常日志）
	 * @param log
	 * @param logData
	 * @return void 返回类型
	 * @throws
	 */
	public static final void multiError(Logger log, TraceLog logData) {
		try {
			String traceId = (String) MDC.get(TraceLog.TRACE_ID);
			logData.setTraceId(traceId);
			logData.setClassMethod(log.getName());
			logData.setEndTime(System.currentTimeMillis());
			logData.setTotalTime(System.currentTimeMillis()-logData.getStartTime());
			logData.setLogType(TraceLog.TYPE_ERROR);
			logData.setRemark(logData.getRemark()==null?""+getStackTrace(logData.getE()):logData.getRemark()+" "+getStackTrace(logData.getE()));
			logData.setE(null);
			
			log.log(Xtrace.XTRACE, objectMapper.writeValueAsString(logData));
		} catch (Exception e) {
			logger.error("打印监控error日志出现异常", e);
		}
	}
	
	/**
	 * @Title: error
	 * @Description: 打印监控异常日志（确保同一线程同一traceId，可打印多条异常日志）
	 * @param log
	 * @param logData
	 * @param parentId
	 * @return void 返回类型
	 * @throws
	 */
	public static final void subError(Logger log, TraceLog logData, String parentId) {
		try {
			String traceId = (String) MDC.get(TraceLog.TRACE_ID);
			logData.setTraceId(traceId);
			logData.setParentId(parentId);
			logData.setClassMethod(log.getName());
			logData.setEndTime(System.currentTimeMillis());
			logData.setCostTime(System.currentTimeMillis()-logData.getStartTime());
			logData.setLogType(TraceLog.TYPE_ERROR);
			logData.setRemark(logData.getRemark()==null?""+getStackTrace(logData.getE()):logData.getRemark()+" "+getStackTrace(logData.getE()));
			logData.setE(null);
			
			log.log(Xtrace.XTRACE, objectMapper.writeValueAsString(logData));
		} catch (Exception e) {
			logger.error("打印监控error日志出现异常", e);
		}
	}
	
	/**
	 * @Title: info
	 * @Description: 打印监控Info日志
	 * @param log
	 * @param logData
	 * @return void 返回类型
	 */
	public static final void info(Logger log, TraceLog logData) {
		try {
			String traceId = (String) MDC.get(TraceLog.TRACE_ID);
			logData.setClassMethod(log.getName());
			logData.setTraceId(traceId);
			logData.setEndTime(System.currentTimeMillis());
			logData.setLogType(TraceLog.TYPE_INFO);
			logData.setE(null);
			
			log.log(Xtrace.XTRACE, objectMapper.writeValueAsString(logData));
		} catch (Exception e) {
			log.error("打印监控Info日志出现异常", e);
		}
	}
	
	
	/**
	 * @Title: info
	 * @Description: 打印监控Info日志
	 * @param log
	 * @param logData
	 * @return void 返回类型
	 */
	public static final void subInfo(Logger log, TraceLog logData, String parentId) {
		try {
			String traceId = genTraceId();
			logData.setClassMethod(log.getName());
			logData.setTraceId(traceId);
			logData.setParentId(parentId);
			logData.setEndTime(System.currentTimeMillis());
			logData.setCostTime(logData.getEndTime()-logData.getStartTime());
			logData.setLogType(TraceLog.TYPE_INFO);
			logData.setE(null);
			
			log.log(Xtrace.XTRACE, objectMapper.writeValueAsString(logData));
		} catch (Exception e) {
			log.error("打印监控Info日志出现异常", e);
		}
	}
	
	/**
	 * @Title: getStackTrace
	 * @Description: 获取异常堆栈信息
	 * @return String 返回类型
	 */
	public static String getStackTrace(Exception e) {
		if(e==null){
        	return "";
        }
        String stackTrace = null;
        Writer result = new StringWriter();
        PrintWriter printWriter = new PrintWriter(result);
        e.printStackTrace(printWriter);
        stackTrace = result.toString();
        if(stackTrace!=null && stackTrace.length()>100){
        	stackTrace = stackTrace.substring(0, 100)+"...";
        }
        return stackTrace;
    }
	
}
